use rand::seq::SliceRandom;
use rand;

use crate::tensor::{self, Tensor};

pub use super::DataSet;

pub struct DataLoader<DS: DataSet> {
    dataset: DS,
    batch_size: usize,
    shuffle: bool,
}

pub struct DataLoaderIter<'a, DS: DataSet> {
    loader: &'a DataLoader<DS>,
    cursor: usize,
    indices: Vec<usize>,
}

pub struct DataLoaderIntoIter<DS: DataSet> {
    loader: DataLoader<DS>,
    cursor: usize,
    indices: Vec<usize>,
}

impl<DS: DataSet> DataLoader<DS> {
    pub fn new(dataset: DS, batch_size: usize, shuffle: bool) -> Self {
        Self { dataset, batch_size, shuffle }
    }

    pub fn batch_count(&self) -> usize {
        self.dataset.len() / self.batch_size
    }

    pub fn iter<'a>(&'a self) -> DataLoaderIter<'a, DS> {
        let indices = self.get_iter_indices();
        DataLoaderIter {
            loader: self, 
            cursor: 0,
            indices,
        } 
    }

    fn get_iter_indices(&self) -> Vec<usize> {
        let len = self.dataset.len();
        if self.shuffle {
            (0..len).collect()
        } else {
            let mut rng = rand::rng();
            let mut indices: Vec<usize> = (0..len).collect();
            indices.shuffle(&mut rng);
            indices
        }
    }
}

impl<DS: DataSet> IntoIterator for DataLoader<DS> {
    type IntoIter = DataLoaderIntoIter<DS>;
    type Item = (Tensor, Tensor);
    fn into_iter(self) -> Self::IntoIter {
        let indices = self.get_iter_indices();
        DataLoaderIntoIter {
            loader: self, 
            cursor: 0,
            indices,
        } 
    }
}

impl<'a, DS: DataSet> Iterator for DataLoaderIter<'a, DS> {
    type Item = (Tensor, Tensor);
    fn next(&mut self) -> Option<Self::Item> {
        iter_next(self.loader, &mut self.cursor, &self.indices)
    }
}

impl<DS: DataSet> Iterator for DataLoaderIntoIter<DS> {
    type Item = (Tensor, Tensor);
    fn next(&mut self) -> Option<Self::Item> {
        iter_next(&self.loader, &mut self.cursor, &self.indices)
    }
}

fn iter_next<DS: DataSet>(loader: &DataLoader<DS>, cursor: &mut usize, indices: &[usize]) -> Option<(Tensor, Tensor)> {
    let begin = *cursor;
    let end = *cursor + loader.batch_size;

    if end > indices.len() {
        None
    } else {
        let mut xs = Vec::new();
        let mut ys = Vec::new();
        for index in begin..end {
            let index = indices[index];
            let (x, y) = loader.dataset.get(index);
            xs.push(x);
            ys.push(y);
        }

        *cursor += loader.batch_size;

        Some((
            tensor::stack(&xs).unwrap(),
            tensor::stack(&ys).unwrap(),
        ))
    }

}